package cn.seaboot.admin.quartz.service;

import cn.seaboot.admin.mvc.l18n.L18N;
import cn.seaboot.admin.quartz.bean.ScheduleJob;
import cn.seaboot.admin.quartz.core.QuartzJobBuilder;
import cn.seaboot.admin.quartz.core.QuartzManager;
import cn.seaboot.admin.quartz.core.QuartzScheduled;
import cn.seaboot.admin.quartz.dao.ScheduleJobDao;
import cn.seaboot.admin.quartz.smart.JobStatus;
import cn.seaboot.commons.core.Asserts;
import cn.seaboot.commons.core.Snowflake;
import cn.seaboot.commons.core.SnowflakeFactory;
import cn.seaboot.commons.exception.ServiceException;
import org.quartz.Job;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * 对 Quartz 进行二次封装，强化了常用函数的代码逻辑，添加了部分默认配置，参数更少，调用更加容易。
 * <p>
 * 就是一份标准的封装
 *
 * @author Mr.css
 * @version 2019/12/7 10:19
 */
@Service
public class ScheduleJobService implements Consumer<QuartzJobBuilder> {
    private final Logger logger = LoggerFactory.getLogger(ScheduleJobService.class);
    private final Snowflake snowflake = SnowflakeFactory.createSnowflake();

    @Resource
    private ScheduleJobDao scheduleJobDao;

    @Resource
    private QuartzManager quartzManager;

    @Resource
    private ApplicationContext applicationContext;


    /**
     * 恢复定时任务
     * <p>
     * 扫描带 QuartzScheduled 注解的类，将其注册到 Schedule 中
     * <p>
     * 因为代码与 quartz 耦合程度非常高，不怎么实用，后期可能考虑删除这一功能。
     *
     * @throws SchedulerException -
     */
    @PostConstruct
    @Transactional(rollbackFor = Exception.class)
    @SuppressWarnings("unchecked")
    public void resumeStandardSchedule() throws SchedulerException {
        logger.info("[--- Configuration ---]Scheduler start up!");

        ApplicationContext context = applicationContext;
        logger.info("QuartzStarter resume standard schedule!");

        // 恢复普通任务，普通任务是通过注解扫描进行恢复的
        String[] names = context.getBeanNamesForAnnotation(QuartzScheduled.class);
        for (String beanName : names) {
            Object bean = context.getBean(beanName);
            if (bean instanceof Job) {
                // if class case failed, stop application
                Class<Job> clazz = (Class<Job>) bean.getClass();
                QuartzScheduled scheduled = clazz.getAnnotation(QuartzScheduled.class);

                // 任务初始化
                ScheduleJob job = this.start(
                        scheduled.jobGroup(), scheduled.jobName(), scheduled.triggerGroup(),
                        scheduled.triggerName(), clazz, scheduled.cron());

                // 如果有必要，启动之后立即执行一次
                if (job.getEager()) {
                    this.triggerJob(job.getId());
                }
            }
        }

        logger.info("[--- Configuration ---]Scheduler loaded!");
    }


    /**
     * 返回一个任务构建器，用于创建任务
     *
     * @return -
     */
    public QuartzJobBuilder builder() {
        return new QuartzJobBuilder(this);
    }

    /**
     * 使用 QuartzJobBuilder 创建一个任务
     *
     * @param quartz the input argument
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void accept(QuartzJobBuilder quartz) {
        try {
            this.inset(quartz.getJobGroup(), quartz.getJobName(),
                    quartz.getTriggerGroup(), quartz.getTriggerName(), () -> {
                        ScheduleJob job = new ScheduleJob();
                        job.setId(snowflake.nextId());
                        job.setEager(quartz.getEager());
                        job.setDescription(quartz.getDescription());

                        job.setCron(quartz.getCron());
                        job.setJobClass(quartz.getJobClass().getName());

                        job.setJobGroup(quartz.getJobGroup());
                        job.setJobName(quartz.getJobName());
                        job.setTriggerGroup(quartz.getTriggerGroup());
                        job.setTriggerName(quartz.getTriggerName());
                        return job;
                    });
        } catch (SchedulerException e) {
            throw new ServiceException(e);
        }
    }


    /**
     * 恢复一个任务
     * <p>
     * 如果数据库已经存在配置，优先使用数据库的配置；如果不存在，则在数据库持久化任务信息。
     * <p>
     * 最终，会在 schedule 添加一个任务，数据库，会增加任务相关的信息。
     *
     * @param jobGroup     分组名称
     * @param jobName      任务名
     * @param triggerGroup 触发器分组
     * @param triggerName  触发器名称
     * @param create       默认的任务
     */
    @Transactional(rollbackFor = Exception.class)
    public ScheduleJob inset(String jobGroup, String jobName,
                             String triggerGroup, String triggerName,
                             Supplier<ScheduleJob> create) throws SchedulerException {
        // 尝试从数据库查询历史配置
        ScheduleJob job = scheduleJobDao.selectByName(jobGroup, jobName, triggerGroup, triggerName);

        if (job == null) {
            // 如果为空，则新建一个任务
            job = create.get();

            scheduleJobDao.insert(job);
            quartzManager.builder().from(job).build();
        } else {
            // 重置状态，系统下线，被强制中断导致状态异常
            if (JobStatus.RUN == JobStatus.valueOf(job.getStatus())) {
                job.setStatus(JobStatus.IDLE.value());
                scheduleJobDao.updateSampleById(job);
            }

            // 非禁用状态，才启动任务
            if (Boolean.FALSE.equals(job.getDisabled())) {
                quartzManager.builder().from(job).build();
            }
        }

        // trigger after startup
        if (Boolean.TRUE.equals(job.getEager())) {
            quartzManager.triggerJob(jobGroup, jobName);
        }

        return job;
    }

    /**
     * 启动一个任务
     * <p>
     * 非常传统的任务启动函数，如果数据库存在配置，则优先使用数据库的配置
     *
     * @param jobClass     全类名
     * @param cron         表达式
     * @param jobGroup     分组
     * @param jobName      任务名
     * @param triggerGroup 触发器分组
     * @param triggerName  触发器名
     * @return ScheduleJob 可用于绑定业务数据
     * @throws SchedulerException 重复添加任务等意外情况
     */
    @Transactional(rollbackFor = Exception.class)
    public ScheduleJob start(String jobGroup, String jobName,
                             String triggerGroup, String triggerName,
                             Class<? extends Job> jobClass, String cron) throws SchedulerException {

        return this.inset(jobGroup, jobName, triggerGroup, triggerName, () -> {
            ScheduleJob job = new ScheduleJob();
            job.setId(snowflake.nextId());
            job.setCron(cron);
            job.setJobClass(jobClass.getName());
            job.setJobGroup(jobGroup);
            job.setJobName(jobName);
            job.setTriggerGroup(triggerGroup);
            job.setTriggerName(triggerName);
            return job;
        });
    }

    /**
     * 删除计划任务
     *
     * @param id ID
     * @return affected rows
     */
    @Transactional(rollbackFor = Exception.class)
    public int deleteById(Long id) throws SchedulerException {
        ScheduleJob job = this.queryById(id);
        Asserts.notNull(job, L18N.Quartz.NOT_FOUND, id);

        int ret = scheduleJobDao.deleteById(id);
        quartzManager.deleteJob(job.getJobGroup(), job.getJobName());
        return ret;
    }

    /**
     * 修改计划任务的表达式
     *
     * @param id   ID
     * @param cron 时间设置，参考quartz说明文档
     * @return affected rows
     * @throws SchedulerException on Error
     */
    @Transactional(rollbackFor = Exception.class)
    public int modifyJobTime(Long id, String cron) throws SchedulerException {
        ScheduleJob job = this.queryById(id);
        Asserts.notNull(job, L18N.Quartz.NOT_FOUND, id);
        job.setCron(cron);

        int ret = scheduleJobDao.updateSampleById(job);
        quartzManager.modifyJob(job.getTriggerGroup(), job.getTriggerName(), job.getCron());
        return ret;
    }

    /**
     * 修改任务的状态
     *
     * @param id     ID
     * @param status 状态
     * @return affected rows
     */
    public int modifyJobStatus(Long id, Integer status) {
        return scheduleJobDao.updateJobStatus(id, status);
    }

    /**
     * 任务启用
     * <p>
     * 数据标记为启用，重新在 schedule 中添加任务
     *
     * @param id ID
     * @return affected rows
     * @throws SchedulerException on Error
     */
    @Transactional(rollbackFor = Exception.class)
    public int resumeJob(Long id) throws SchedulerException {
        ScheduleJob job = this.queryById(id);
        Asserts.notNull(job, L18N.Quartz.NOT_FOUND, id);
        if (Boolean.TRUE.equals(job.getDisabled())) {
            job.setDisabled(Boolean.FALSE);

            // 启动任务
            quartzManager.builder().from(job).build();

            return scheduleJobDao.updateSampleById(job);
        } else {
            return 0;
        }
    }

    /**
     * 任务禁用
     * <p>
     * 数据标记为禁用，之后删除 schedule 中的任务
     *
     * @param id ID
     * @return affected rows
     * @throws SchedulerException on Error
     */
    @Transactional(rollbackFor = Exception.class)
    public int disableJob(Long id) throws SchedulerException {
        ScheduleJob job = this.queryById(id);
        Asserts.notNull(job, L18N.Quartz.NOT_FOUND, id);

        if (Boolean.FALSE.equals(job.getDisabled())) {
            job.setDisabled(Boolean.TRUE);
            int ret = scheduleJobDao.updateSampleById(job);

            quartzManager.deleteJob(job.getJobGroup(), job.getJobName());
            return ret;
        } else {
            return 0;
        }
    }

    /**
     * 触发任务
     *
     * @param id ID
     */
    @Transactional(rollbackFor = Exception.class)
    public void triggerJob(Long id) throws SchedulerException {
        ScheduleJob job = this.queryById(id);
        Asserts.notNull(job, L18N.Quartz.NOT_FOUND, id);
        if (Boolean.FALSE.equals(job.getDisabled())) {
            quartzManager.triggerJob(job.getJobGroup(), job.getJobName());
        }
    }

    /**
     * 查询配置信息
     *
     * @param id ID
     * @return pojo
     */
    public ScheduleJob queryById(Long id) {
        return scheduleJobDao.selectById(id);
    }

    /**
     * 查
     *
     * @param jobGroup     分组
     * @param jobName      任务名
     * @param triggerGroup 触发器分组
     * @param triggerName  触发器名
     * @return affected rows
     */
    public ScheduleJob selectByName(String jobGroup, String jobName,
                                    String triggerGroup, String triggerName) {
        return scheduleJobDao.selectByName(jobGroup, jobName, triggerGroup, triggerName);
    }


    /**
     * 查列表
     *
     * @param params map
     * @return listMap
     */
    public List<ScheduleJob> queryList(Map<String, Object> params) {
        return scheduleJobDao.selectList(params);
    }

    /**
     * 获取未来n次的触发时间
     *
     * @param cron  表达式
     * @param times 次数
     * @return 时间
     */
    public List<Date> getNextFireTime(String cron, int times) {
        return quartzManager.getNextFireTime(cron, times);
    }
}
